import {MainCanvas} from "./const.ts";
import {GameMap} from "./GameMap.ts";
import {level1} from "./data.ts";

export const mainCtx = MainCanvas.getContext("2d")!

export interface EventModel {
    onkeyup(e: KeyboardEvent): void

    onkeydown(e: KeyboardEvent): void
}

export interface GMap {
    render(ctx: CanvasRenderingContext2D): void
}

export abstract class GameModel {
    x = 0;
    y = 0
    width = 0
    height = 0
    compactLevel = 0

    abstract render(): void;

    abstract update(time: number): void

    ctx: CanvasRenderingContext2D

    protected constructor(x: number, y: number, width: number, height: number, ctx: CanvasRenderingContext2D) {
        this.ctx = ctx
        this.x = x
        this.y = y
        this.width = width
        this.height = height
    }

    reg() {
        models.push(this)
        if (isEventModel(this)) {
            eventModels.push(this)
        }
    }

    destroy() {
        models.splice(models.indexOf(this), 1)
        if (isEventModel(this)) {
            eventModels.splice(eventModels.indexOf(this), 1)
        }
    }

    abstract compact(model: GameModel|GameMap): void
}

export abstract class MoveableModel extends GameModel {
    direction = 'top'
    moving = false
    speed = 2
    _preTime = 0

    update(time: number) {
        if (this._preTime === 0) {
            this._preTime = time
            return
        }
        let elapsed = time - this._preTime
        this._preTime = time
        if (this.moving) {
            modelMove(this, elapsed, gameMap)
        }
    }

    // abstract move(): void
}

const models: GameModel[] = []
const eventModels: EventModel[] = []
const gameMap = new GameMap(level1)


function isModelImpact(model: MoveableModel) {

    for (let m of models) {
        if (m === model){
            continue
        }
        let w = (m.width + model.width) / 2
        let h = (m.height + model.height) / 2
        if (Math.abs(m.x - model.x) < w && Math.abs(m.y - model.y) < h) {
            model.compact(m)
            return
        }
    }
}

export function impact(model: MoveableModel, d: number, m: GameMap) {
    let tile = 16
    let map = m.map
    let x1 = model.x - model.width / 2
    let y1 = model.y - model.height / 2
    let x2 = x1 + model.width
    let y2 = y1 + model.height
    let i1 = Math.floor(x1 / tile)
    let i2 = Math.floor((x2 - 1) / tile)
    let j1 = Math.floor(y1 / tile)
    let j2 = Math.floor((y2 - 1) / tile)
    let im = false
    switch (model.direction) {
        case 'top':
            y1 = Math.max(y1 - d, 0)
            if (y1 == 0) {
                model.compact(m)
                break
            }
            j1 = Math.floor(y1 / tile)
            for (let j = j2; j >= j1; j--) {
                for (let i = i1; i <= i2; i++) {
                    if (map[j][i] !== 0) {
                        m.impact(j, i, model.compactLevel)
                        im = true
                    }
                }
                if (im) {
                    model.compact(m)
                    y1 = (j + 1) * tile
                    break
                }
            }
            model.y = y1 + model.height / 2
            break
        case 'bottom':
            y2 = Math.min(y2 + d, map.length * tile)
            if (y2 == map.length * tile) {
                model.compact(m)
                break
            }
            j2 = Math.floor((y2 - 1) / tile)
            for (let j = j1; j <= j2; j++) {
                for (let i = i1; i <= i2; i++) {
                    if (map[j][i] !== 0) {
                        m.impact(j, i, model.compactLevel)
                        im = true
                    }
                }
                if (im) {
                    model.compact(m)
                    y2 = j * tile
                    break
                }
            }
            model.y = y2 - model.height / 2
            break
        case 'left':
            x1 = Math.max(x1 - d, 0)
            if (x1 == 0) {
                model.compact(m)
                break
            }
            i1 = Math.floor(x1 / tile)
            for (let i = i2; i >= i1; i--) {
                for (let j = j1; j <= j2; j++) {
                    if (map[j][i] !== 0) {
                        m.impact(j, i, model.compactLevel)
                        im = true
                    }
                }
                if (im) {
                    model.compact(m)
                    x1 = (i + 1) * tile
                    break
                }
            }
            model.x = x1 + model.width / 2
            break
        case 'right':
            x2 = Math.min(x2 + d, map[0].length * tile)
            if (x2 == map[0].length * tile) {
                model.compact(m)
                break
            }
            i2 = Math.floor((x2 - 1) / tile)
            for (let i = i1; i <= i2; i++) {
                for (let j = j1; j <= j2; j++) {
                    if (map[j][i] !== 0) {
                        m.impact(j, i, model.compactLevel)
                        im = true
                    }
                }
                if (im) {
                    model.compact(m)
                    x2 = i * tile
                    break
                }
            }
            model.x = x2 - model.width / 2
            break
    }
}

export function modelMove(model: MoveableModel, elapsed: number, m: GameMap) {
    let d = elapsed * model.speed / 1000 * 60
    impact(model, d, m)
    isModelImpact(model)
}


export function isEventModel(model: any): model is EventModel {
    return (model as any).onkeyup !== undefined
}

export function isMap(model: any): model is GameMap {
    return (model as any).map !== undefined
}
function update(time: number) {
    // mainCtx.fillStyle = 'blue'
    mainCtx.fillRect(0, 0, 500, 500)
    gameMap.render(mainCtx)
    for (let model of models) {
        model.update(time)
        model.render()
    }
    requestAnimationFrame((time) => {
        update(time)
    })
}

export function enginStart() {
    update(0)
    window.onkeyup = (e) => {
        for (let model of eventModels) {
            model.onkeyup(e)
        }
    }
    window.onkeydown = (e) => {
        for (let model of eventModels) {
            model.onkeydown(e)
        }
    }
}

